package com.faker.tools.util;

import com.alibaba.fastjson2.JSONObject;
import com.faker.tools.model.dto.GitlabSetting;
import lombok.extern.slf4j.Slf4j;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.models.Project;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * <p>SyncGitLabUtil</p>
 *
 * <p>项目名称：gitlabSync</p>
 *
 * <p>注释:无</p>
 *
 * <p>Copyright: Copyright Faker(c) 2024/9/26</p>
 *
 * <p>公司: Faker</p>
 *
 * @author 淡梦如烟
 * @version 1.0
 * @date 2024/9/26 上午10:32
 */
@Slf4j
public class SyncGitLabUtil {

    /**
     * 源Git服务器配置
     */
    public static GitLabApi SOURCE_GITLAB_API = null;

    /**
     * 目的Git服务器配置
     */
    public static GitLabApi DEST_GITLAB_API = null;

    /**
     * 源Ggitlab配置
     */
    public static GitlabSetting sourceGitLabSetting = null;

    /**
     * 目的gitlab配置
     */
    public static GitlabSetting destGitLabSetting = null;

    /**
     * 初始化gitlab配置
     *
     * @throws FileNotFoundException
     */
    public static void init() throws FileNotFoundException {

        Map<String, Object> ymlMap = YmlUtil.loadYml();
        if (null != ymlMap.get("gitlab") && ymlMap.get("gitlab") instanceof Map) {
            Map gitlab = (Map) ymlMap.get("gitlab");
            Object source = gitlab.get("source");
            Object dest = gitlab.get("dest");
            sourceGitLabSetting = JSONObject.parseObject(JSONObject.toJSONString(source), GitlabSetting.class);
            destGitLabSetting = JSONObject.parseObject(JSONObject.toJSONString(dest), GitlabSetting.class);
        }
        if (null == sourceGitLabSetting || null == destGitLabSetting) {
            log.error("请检查配置文件gitlab是否正确");
            throw new RuntimeException("请检查配置文件gitlab是否正确");
        }
        log.info("源Git服务器配置:{}", sourceGitLabSetting);
        log.info("目的Git服务器配置:{}", destGitLabSetting);


        // 创建girlabApi对象
        SOURCE_GITLAB_API = new GitLabApi("http://" + sourceGitLabSetting.getUrl(), sourceGitLabSetting.getToken());
        DEST_GITLAB_API = new GitLabApi("http://" + destGitLabSetting.getUrl(), destGitLabSetting.getToken());
    }

    /**
     * 同步gitlab项目
     *
     * @throws Exception
     */
    public static void sync() throws Exception {
        List<Project> sourceProjects = SOURCE_GITLAB_API.getProjectApi().getProjects();
        List<Project> destProjects = DEST_GITLAB_API.getProjectApi().getProjects();

        //比较项目的path，如果不存在，则创建
        List<Project> createProjects = new ArrayList<>();
        for (Project project : sourceProjects) {
            boolean exist = false;
            for (Project destProject : destProjects) {
                if (project.getPathWithNamespace().equals(destProject.getPathWithNamespace())) {
                    exist = true;
                    break;
                }
            }
            if (!exist) {
                createProjects.add(project);
            }
        }
        log.info("需要创建的项目:{}", createProjects.stream().map(Project::getName).collect(Collectors.joining("\n")));
        int total = createProjects.size();
        final AtomicInteger success = new AtomicInteger(0);
        ExecutorService executorService = Executors.newFixedThreadPool(12);
        boolean lock = true;
        for (int i = 0; i < total; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                Project project = createProjects.get(finalI);
                try {
                    File locGitDir = cloneRepo(project);
                    Project destProject = null;
                    boolean isCreated = false;
                    int times = 0;
                    while (!isCreated) {
                        try {
                            destProject = DEST_GITLAB_API.getProjectApi().createProject(project);
                            isCreated = true;
                        } catch (GitLabApiException e) {
                            times++;
                            log.warn("gitlab项目[" + project.getNameWithNamespace() + "]创建失败,正在重试[" + times + "]:");
                            if (times > 5) {
                                log.error("gitlab项目创建失败:" + project.getNameWithNamespace());
                                isCreated = true;
                                throw new RuntimeException(e);
                            }
                            Thread.sleep(times * 1000);
                        }
                    }
                    log.info("gitlab项目创建成功:{}", destProject.getNameWithNamespace());
                    pushRepo(destProject, locGitDir);
                    log.info("gitlab同步[" + (finalI + 1) + "/" + total + "]项目成功:{}", destProject.getNameWithNamespace());

                    //删除本地仓库
                    Thread.sleep(1000);
                    locGitDir.delete();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    success.incrementAndGet();
                }
            });
            executorService.execute(thread);
        }
        int times = 1;
        while (success.get() < total) {
            if (times++ % 10 == 0) {
                log.info("同步gitlab项目中:{}/{}", success.get(), total);
                times = 1;
            }

            Thread.sleep(1000);
        }
        executorService.shutdown();
        log.info("同步gitlab项目完成");

    }

    /**
     * 同步gitlab项目
     *
     * @throws Exception
     */
    public static void clonePtp(String[] projectNames) throws Exception {
        List<Project> sourceProjects = SOURCE_GITLAB_API.getProjectApi().getProjects();
        List<Project> destProjects = DEST_GITLAB_API.getProjectApi().getProjects();

        List<String> projectNamesList = Arrays.asList(projectNames);
        //比较项目的path，如果不存在，则创建
        List<Project> createProjects = new ArrayList<>();
        log.info("" + projectNamesList);
        for (Project project : sourceProjects) {
//            if (projectNamesList.contains(project.getPathWithNamespace())) {
//                log.info("gitlab项目[" + project.getNameWithNamespace() + "]已存在");
//                createProjects.add(project);
//            }
            for (String projectName : projectNames) {
                if (project.getPath().toLowerCase().equalsIgnoreCase(projectName.toLowerCase())) {
                    log.info("gitlab项目[" + project.getNameWithNamespace() + "]已添加");
                    createProjects.add(project);
                }
            }
        }
        log.info("需要创建的项目:{}", createProjects.stream().map(Project::getName).collect(Collectors.joining("\n")));
        if (1 == 1) {
            return;
        }
        int total = createProjects.size();
        final AtomicInteger success = new AtomicInteger(0);
        ExecutorService executorService = Executors.newFixedThreadPool(12);
        boolean lock = true;
        for (int i = 0; i < total; i++) {
            int finalI = i;
            Thread thread = new Thread(() -> {
                Project project = createProjects.get(finalI);
                try {
                    File locGitDir = cloneRepo(project);
                    Project destProject = null;
                    for (Project projectTemp : destProjects) {
                        if (projectTemp.getPathWithNamespace().equals(project.getPathWithNamespace())) {
                            destProject = projectTemp;
                            break;
                        }
                    }
                    if (destProject == null) {
                        log.error("gitlab项目[" + project.getNameWithNamespace() + "]不存在");
                        return;
                    }
                    pushRepo(destProject, locGitDir);
                    log.info("gitlab同步[" + (finalI + 1) + "/" + total + "]项目成功:{}", destProject.getNameWithNamespace());

                    //删除本地仓库
                    Thread.sleep(1000);
                    locGitDir.delete();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    success.incrementAndGet();
                }
            });
            executorService.execute(thread);
        }
        int times = 1;
        while (success.get() < total) {
            if (times++ % 10 == 0) {
                log.info("同步gitlab项目中:{}/{}", success.get(), total);
                times = 1;
            }

            Thread.sleep(1000);
        }
        executorService.shutdown();
        log.info("同步gitlab项目完成");

    }

    /**
     * 克隆仓库
     *
     * @param project gitlab项目
     * @return 是否克隆成功
     */
    public static File cloneRepo(Project project) throws IOException, InterruptedException {
        log.info("克隆仓库:{}", project.getPath());
        //转换成绝对路径
        File dir = new File("target/git/" + project.getNamespace().getPath());
        File tempDir = new File(dir.getAbsolutePath());
        if (!tempDir.getParentFile().exists()) {
            tempDir.getParentFile().mkdirs();
        }
        File gitDir = new File(dir.getAbsolutePath() + "/" + project.getPath());
        if (!gitDir.getParentFile().exists()) {
            gitDir.getParentFile().mkdirs();
        }
        if (gitDir.exists()) {
            boolean isDelete = gitDir.delete();
            log.info("克隆仓库本地已经存在：{},是否删除成功：" + isDelete, gitDir.getAbsolutePath());
            if (!isDelete) {
                log.error("克隆仓库本地已经存在：{},但是删除失败:" + gitDir.getAbsolutePath());
                //linux下使用命令删除才能删掉
                Process process = Runtime.getRuntime().exec(new String[]{"rm", "-rf", gitDir.getAbsolutePath()});
                process.waitFor();
//                throw new IOException("克隆仓库本地已经存在：{},但是删除失败:" + gitDir.getAbsolutePath());
            }
        }

        String remoteUrl = "";
        if (project.getHttpUrlToRepo().toLowerCase().startsWith("https://")) {
            remoteUrl = "https://";
        } else {
            remoteUrl = "http://";
        }
        remoteUrl += sourceGitLabSetting.getUsername() + ":" + sourceGitLabSetting.getToken() + "@";
        remoteUrl += sourceGitLabSetting.getUrl() + "/" + project.getPathWithNamespace() + ".git";

        log.info("克隆仓库:{}", remoteUrl);
        String[] shells = {"git", "clone", "--bare", remoteUrl, project.getPath()};
        log.info("执行命令:{}", Arrays.stream(shells).collect(Collectors.joining(" ")));
        String[] envp = {"PATH=/usr/local/bin:/usr/bin:/bin"};
        Process process = Runtime.getRuntime().exec(shells, envp, tempDir);
        BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
        BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        // 读取输出信息
        String s = null;
        while ((s = stdInput.readLine()) != null) {
            log.info(s);
        }

        // 读取错误信息
        while ((s = stdError.readLine()) != null) {
            log.error(s);
        }

        process.waitFor();

        // 关闭流
        stdInput.close();
        stdError.close();

        if (process.exitValue() != 0) {
            log.error("克隆失败:{}", project.getPath());
            process.destroy();
            throw new RuntimeException("克隆失败:{}" + project.getPath());
        }
        process.destroy();
        return gitDir;
    }

    /**
     * 镜像推送
     *
     * @param project
     * @param locGitDir
     * @throws IOException
     * @throws InterruptedException
     */
    private static void pushRepo(Project project, File locGitDir) throws IOException, InterruptedException {
        log.info("推送仓库:{}", project.getPath());

        String remoteUrl = "";
        if (project.getHttpUrlToRepo().toLowerCase().startsWith("https://")) {
            remoteUrl = "https://";
        } else {
            remoteUrl = "http://";
        }
        remoteUrl += destGitLabSetting.getUsername() + ":" + destGitLabSetting.getToken() + "@";
        remoteUrl += destGitLabSetting.getUrl() + "/" + project.getPathWithNamespace() + ".git";

        log.info("镜像推送仓库:{}", remoteUrl);
        String[] shells = {"git", "push", "--mirror", remoteUrl};
        log.info("执行命令:{}", Arrays.stream(shells).collect(Collectors.joining(" ")));
        String[] envp = {"PATH=/usr/local/bin:/usr/bin:/bin"};
        Process process = Runtime.getRuntime().exec(shells, envp, locGitDir);

        BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
        BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        // 读取输出信息
        String s = null;
        while ((s = stdInput.readLine()) != null) {
            log.info(s);
        }

        // 读取错误信息
        while ((s = stdError.readLine()) != null) {
            log.error(s);
        }
        process.waitFor();

        // 关闭流
        stdInput.close();
        stdError.close();

        if (process.exitValue() != 0) {
            log.error("推送失败:{}", project.getPath());
            int len;
            byte[] buffer = new byte[1024];
            InputStream errorIs = process.getErrorStream();
            while ((len = errorIs.read(buffer)) != -1) {
                log.error(new String(buffer, 0, len));
            }
            errorIs.close();
            process.destroy();
            throw new RuntimeException("推送失败:{}" + project.getPath());
        }
        process.destroy();
    }

    /**
     * 删除gitlab项目
     *
     * @param gitLabApi gitlab api
     * @throws GitLabApiException gitlab api异常
     */
    public static void delete(GitLabApi gitLabApi) throws GitLabApiException {
        List<Project> deleteProject = gitLabApi.getProjectApi().getProjects();
        for (Project project : deleteProject) {
            gitLabApi.getProjectApi().deleteProject(project);
            log.info("gitlab项目删除成功:{}", project.getNameWithNamespace());
        }
        log.info("gitlab项目所有删除成功:{}", deleteProject.size());
    }

    /**
     * 点对点同步gitlab项目
     * 适用于2个gitlab服务器之间网络畅通，能互相访问的情况
     *
     * @throws GitLabApiException   gitlab api异常
     * @throws InterruptedException
     */
    public static void syncPtp() throws GitLabApiException, InterruptedException {

        List<Project> sourceProjects = SOURCE_GITLAB_API.getProjectApi().getProjects();
        List<Project> destProjects = DEST_GITLAB_API.getProjectApi().getProjects();

        //比较项目的path，如果不存在，则创建
        List<Project> createProjects = new ArrayList<>();
        for (Project project : sourceProjects) {
            boolean exist = false;
            for (Project destProject : destProjects) {
                if (project.getPathWithNamespace().equals(destProject.getPathWithNamespace())) {
                    exist = true;
                    break;
                }
            }
            if (!exist) {
                createProjects.add(project);
            }
        }
        log.info("需要创建的项目:{}", createProjects.stream().map(Project::getName).collect(Collectors.joining("\n")));
        int total = createProjects.size();
        final AtomicInteger success = new AtomicInteger(0);

        boolean lock = true;
        for (int i = 0; i < total; i++) {
            Project project = createProjects.get(i);

            String remoteUrl = "";
            if (project.getHttpUrlToRepo().toLowerCase().startsWith("https://")) {
                remoteUrl = "https://";
            } else {
                remoteUrl = "http://";
            }
            remoteUrl += sourceGitLabSetting.getUsername() + ":" + sourceGitLabSetting.getToken() + "@";
            remoteUrl += sourceGitLabSetting.getUrl() + "/" + project.getPathWithNamespace() + ".git";

            log.info("克隆仓库:{}", remoteUrl);

            boolean isCreated = false;
            int times = 0;
            while (!isCreated) {
                try {
                    Project destProject = DEST_GITLAB_API.getProjectApi().createProject(project, remoteUrl);
                    isCreated = true;
                    log.info("gitlab项目[" + (i + 1) + "/" + total + "][" + project.getNameWithNamespace() + "]创建成功:" + destProject);
                } catch (GitLabApiException e) {
                    times++;
                    log.warn("gitlab项目[" + project.getNameWithNamespace() + "]创建失败,正在重试[" + times + "]:");
                    if (times > 5) {
                        log.error("gitlab项目创建失败:" + project.getNameWithNamespace());
                        isCreated = true;
                        e.printStackTrace();
                    }
                    Thread.sleep(times * 1000);
                }
            }

        }

        log.info("同步gitlab项目完成");
    }
}
